package com.zkh.myutils.net;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;

import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;

import com.zkh.myutils.io.IOUtils;
import com.zkh.myutils.io.log.Logger;
import com.zkh.myutils.net.ftp.FTPReply;
import com.zkh.myutils.net.utils.Reply;
import com.zkh.myutils.net.utils.TransferEventListenerHandler;

/**
 * Socket客户端封装
 * @author zkh
 */
public abstract class SocketClient {
	//Socket工厂
	protected SocketFactory socketFactory = SocketFactory.getDefault();
	//serverSocket工厂
	protected ServerSocketFactory serverSocketFactory = ServerSocketFactory.getDefault();
	//Socket连接
	protected Socket socket;
	//Socket输入流
	protected InputStream inputStream;
	//Socket输出流
	protected OutputStream outputStream;
	//读/写句柄，为了能够快速读写信息
	protected BufferedReader reader;
	protected BufferedWriter writer;
	/**
	 * socketInputstream.read()阻塞超时时间，单位：毫秒
	 */
	protected int soTimeout = 0;
	/**
	 * 连接超时时间，单位：毫秒
	 */
	protected int connectTimeout = 0;
	//缓冲大小
	private int receiveBufferSize = -1;
	private int sendBufferSize = -1;
	//传输事件监听器处理器
	protected TransferEventListenerHandler handler;
	//字符集
	private Charset charset = Charset.defaultCharset();
	
	/**客户端和服务端之间交互的相关变量**/
	//响应码
	protected int replyCode;
	//是否是新的响应字符
	protected boolean isNewReplyString;
	//响应行
	protected List<String> replyLines = new ArrayList<>();
	//响应字符
	protected String replyString;
	//响应码为前3个字符（子类可以重写该配置）
	protected int replyCodeLen = 3;
	
	/**
	 * 建立Socket连接
	 * @param host 主机名
	 * @param port 端口
	 */
	public void connect(String host, int port) throws IOException{
		connect(InetAddress.getByName(host), port);
	}
	
	/**
	 * 建立Socket连接
	 * @param host 主机
	 * @param port 端口
	 */
	public void connect(InetAddress host, int port) throws IOException{
		connect(host, port, null, 0);
	}
	
	/**
	 * 建立Socket连接，并绑定本地地址
	 * @param host 主机
	 * @param port 端口
	 * @param localAddr 本地地址。为null时不绑定本地地址
	 * @param localPort 本地端口
	 */
	public void connect(String host, int port, InetAddress localAddr, int localPort) throws IOException{
		connect(InetAddress.getByName(host), port, localAddr, localPort);
	}
	
	/**
	 * 建立Socket连接，并绑定本地地址
	 * @param host 主机
	 * @param port 端口
	 * @param localAddr 本地地址。为null时不绑定本地地址
	 * @param localPort 本地端口
	 */
	public void connect(InetAddress host, int port, InetAddress localAddr, int localPort) throws IOException {
		//初始化连接
		socket = socketFactory.createSocket();
		//设置缓冲大小
		if(receiveBufferSize!=-1) socket.setReceiveBufferSize(receiveBufferSize);
		if(sendBufferSize!=-1) socket.setSendBufferSize(sendBufferSize);
		//绑定本地地址
		if(localAddr!=null) {
			socket.bind(new InetSocketAddress(localAddr, localPort));
		}
		//建立连接
		socket.connect(new InetSocketAddress(host, port), connectTimeout);
		//设置超时
		socket.setSoTimeout(soTimeout);
		//获取流句柄
		inputStream = socket.getInputStream();
		outputStream = socket.getOutputStream();
		//初始化
		reader = new BufferedReader(new InputStreamReader(inputStream, charset));
		writer = new BufferedWriter(new OutputStreamWriter(outputStream, charset));
		//后置操作
		connect();
	}
	
	/**
	 * 连接后置操作，供子类调用
	 */
	protected void connect() throws IOException{
		
	}
	
	/**
	 * 是否已连接
	 */
	public boolean isConnected() {
		return socket!=null && socket.isConnected();
	}
	
	/**
	 * Socket连接是否有效
	 */
	public boolean isAvailable() {
		//已连接
		if(isConnected()) {
			//已关闭
			if(socket.getInetAddress()==null || socket.getPort()==0 || socket.getRemoteSocketAddress()==null 
					|| socket.isClosed() || socket.isInputShutdown() || socket.isOutputShutdown()) {
				return false;
			}
			try {
				//确保能正常获取流
				socket.getInputStream();
				socket.getOutputStream();
				return true;
			}catch(IOException e) {
				Logger.error(e);
			}
		}
		return false;
	}
	
	/**
	 * 断开Socket连接
	 */
	public void disconnect() {
		//关闭变量
		IOUtils.closeQuietly(socket, reader, writer, inputStream, outputStream);
		//销毁变量
		socket=null;reader=null;writer=null;inputStream=null;outputStream=null;
		isNewReplyString = false;replyString = null;
	}

	/**
	 * 设置Socket工厂
	 */
	public void setSocketFactory(SocketFactory socketFactory) {
		this.socketFactory = socketFactory;
	}
	/**
	 * 设置ServerSocket工厂
	 */
	public void setServerSocketFactory(ServerSocketFactory serverSocketFactory) {
		this.serverSocketFactory = serverSocketFactory;
	}
	/**
	 * 设置socketInputstream.read()阻塞超时时间
	 * @param soTimeout socketInputstream.read()阻塞超时时间，单位：毫秒
	 */
	public void setSoTimeout(int soTimeout) {
		this.soTimeout = soTimeout;
	}
	/**
	 * 设置连接超时时间
	 * @param connectTimeout 连接超时时间
	 */
	public void setConnectTimeout(int connectTimeout) {
		this.connectTimeout = connectTimeout;
	}
	/**
	 * 设置接收缓冲大小
	 */
	public void setReceiveBufferSize(int size) {
		this.receiveBufferSize = size;
	}
	/**
	 * 设置发送缓冲大小
	 */
	public void setSendBufferSize(int size) {
		this.sendBufferSize = size;
	}
	public void setSoLinger(boolean soLinger, int linger) throws SocketException {
		socket.setSoLinger(soLinger, linger);
	}
	/**
	 * 设置TCP无延迟
	 */
	public void setTcpNoDelay(boolean tcpNoDelay) throws SocketException {
		socket.setTcpNoDelay(tcpNoDelay);
	}
	/**
	 * 设置保持在线
	 */
	public void setKeepAlive(boolean keepAlive) throws SocketException {
		socket.setKeepAlive(keepAlive);
	}
	/**
	 * 设置响应码长度。默认为3
	 * @param replyCodeLen 响应码长度
	 */
	public void setReplyCodeLen(int replyCodeLen) {
		this.replyCodeLen = replyCodeLen;
	}

	/**
	 * 设置字符串
	 */
	public void setCharset(Charset charset) {
		this.charset = charset;
	}
	/**
	 * 获取编码
	 */
	public Charset getCharset() {
		return charset;
	}
	/**
	 * 获取本地端口
	 */
	public int getLocalPort() {
		return socket.getLocalPort();
	}
	/**
	 * 获取本地地址
	 */
	public InetAddress getLocalAddress() {
		return socket.getLocalAddress();
	}
	/**
	 * 获取远程地址
	 */
	public InetAddress getRemoteAddress(){
		return socket.getInetAddress();
	}
	/**
	 * 获取服务器端口
	 */
	public int getRemotePort() {
		return socket.getPort();
	}
	
	/*********** 处理交互动作 ***********/
	/**
	 * 发送命令
	 * @param command 命令
	 */
	public int sendCommand(String command) throws IOException {
		return sendCommand(command, null);
	}
	
	/**
	 * 发送命令
	 * @param command 命令
	 * @param args 参数
	 */
	public int sendCommand(String command, String args) throws IOException {
		if(writer==null) {
			throw new IOException("连接未开启");
		}
		//组装命令
		String message = command + (args!=null ? ' ' + args : "");
		try {
			//发送命令
			writer.write(message + "\r\n");
			writer.flush();
		}catch(IOException e) {
			if(!isConnected()) {
				throw new IOException("连接异常关闭");
			}
			throw e;
		}
		//触发命令发送
		fireCommandSent(command, message);
		//获取响应
		getReply();
		//返回响应码
		return replyCode;
	}
	
	/**
	 * 获取响应码
	 */
	public int getReplyCode() {
		return replyCode;
	}
	
	/**
	 * 某些方法无法完成整个FTP命令，所以需要手动完成后序操作，并验证成功
	 */
	public boolean completePendingCommand() throws IOException{
		return Reply.isPositiveCompletion(getReply());
	}
	
	/**
	 * 获取回复字符串
	 */
	public String[] getReplyStrings() {
		return replyLines.toArray(new String[replyLines.size()]);
	}
	
	/**
	 * 获取回复字符串
	 */
	public String getReplyString() {
		//已存在，直接返回
		if(!isNewReplyString) {
			return replyString;
		}
		//拼接响应
		StringBuilder sb = new StringBuilder(256);
		replyLines.forEach(e->sb.append(e).append("\r\n"));
		//标记已解析
		isNewReplyString = false;
		//保存并返回
		return (replyString = sb.toString());
	}
	
	/**
	 * 获取响应
	 */
	protected int getReply() throws IOException {
		//新字符串
		isNewReplyString = true;
		//清空缓存数据
		replyLines.clear();
		//读取数据
		String line = reader.readLine();
		//为空
		if(line==null) {
			throw new IOException("连接已关闭");
		}
		//响应长度
		int len = line.length();
		//校验长度
		if(len<replyCodeLen) {
			throw new IOException("响应结果异常");
		}
		//获取响应码
		String code = null;
		try {
			code = line.substring(0, replyCodeLen);
			replyCode = Integer.parseInt(code);
		}catch(NumberFormatException e) {
			throw new IOException("响应结果异常");
		}
		//保存行结果
		replyLines.add(line);
		
		//获取更多行消息（如果有）
		if (len > replyCodeLen && line.charAt(replyCodeLen) == '-') {
			do{
				line = reader.readLine();
				//为空
				if (line == null) {
					throw new IOException("响应结果异常");
				}
				replyLines.add(line);
			} while (!(line.length() > replyCodeLen && line.charAt(replyCodeLen) != '-' && Character.isDigit(line.charAt(0))) || !(line.startsWith(code) && line.charAt(replyCodeLen) == ' '));
		}
		// 触发接收到消息的回复
		fireReplyReceived(replyCode, String.join("\r\n", replyLines));
		//服务不可用
		if (replyCode == FTPReply.SERVICE_NOT_AVAILABLE) {
			throw new IOException("服务连接已关闭");
		}
		//返回响应码
		return replyCode;
	}
	
	/**
	 * 当接收到响应时，由子类调用，以触发监听器
	 * @param replyCode 响应代码
	 */
	protected void fireReplyReceived(int replyCode, String reply) {
		if(handler!=null && handler.getListenerCount()>0) {
			//触发监听器
			handler.fireReplyReceived(replyCode, reply);
		}
	}
	
	/**
	 * 当发送命令时，由子类调用，以触发监听器
	 * @param command 命令
	 * @param message 消息
	 */
	protected void fireCommandSent(String command, String message) {
		if(handler!=null && handler.getListenerCount()>0) {
			//触发监听器
			handler.fireCommandSent(command, message);
		}
	}
	
	/**
	 * 初始化传输事件监听器处理器，由子类调用
	 */
	protected void initCommandEventListenerHandler(Object obj) {
		handler = new TransferEventListenerHandler(obj);
	}
	
	/**
	 * 获取传输事件监听器处理器，用来添加、移除监听器
	 */
	public TransferEventListenerHandler getTransferEventListenerHandler() {
		return handler;
	}
}
